from sqlalchemy import select
from sqlalchemy.exc import DBAPIError, IntegrityError

from src.core.exceptions.common import RepoError
from src.core.exceptions.user import (
    UserIdAlreadyExist,
    UserIdNotExist,
    UserPhoneNotExist,
    UserTgIdAlreadyExist,
    UserTgIdNotExist,
)
from src.core.models.dto import UserDTO
from src.database.converters.user import (
    convert_db_model_to_user_dto,
    convert_db_model_to_user_entity,
    convert_user_entity_to_db_model,
)
from src.database.exception_mapper import exception_mapper
from src.database.models import User
from src.database.repositories.base import SQLAlchemyRepo
from src.domain.user import UserEntity


class UserReaderImpl(SQLAlchemyRepo):
    @exception_mapper
    async def get_user_by_id(self, user_id: int) -> UserDTO:
        """Получаем пользователя по id"""

        user: User | None = await self._session.scalar(
            select(User).where(
                User.id == user_id,
            )
        )
        if not user:
            raise UserIdNotExist

        user_dto = convert_db_model_to_user_dto(user)

        return user_dto

    @exception_mapper
    async def get_user_by_tg_id(self, user_tg_id: int) -> UserDTO:
        """Получаем пользователя по telegram id"""

        user: User | None = await self._session.scalar(
            select(User).where(
                User.telegram_id == user_tg_id,
            )
        )
        if not user:
            raise UserTgIdNotExist(user_tg_id)

        user_dto = convert_db_model_to_user_dto(user)

        return user_dto

    @exception_mapper
    async def get_user_by_phone_number(self, phone_number: int) -> UserDTO:
        """Получаем пользователя по номеру телефона"""

        user: User | None = await self._session.scalar(
            select(User).where(
                User.phone == phone_number,
            )
        )
        if not user:
            raise UserPhoneNotExist(phone_number)

        user_dto = convert_db_model_to_user_dto(user)

        return user_dto


class UserRepoImpl(SQLAlchemyRepo):
    @exception_mapper
    async def acquire_user_by_id(self, user_id: int) -> UserEntity:
        """Получаем пользователя по id"""

        user: User | None = await self._session.scalar(
            select(User)
            .where(
                User.id == user_id,
            )
            .with_for_update()
        )
        if not user:
            raise UserIdNotExist

        return convert_db_model_to_user_entity(user)

    @exception_mapper
    async def acquire_user_by_tg_id(self, user_tg_id: int) -> UserEntity:
        """Получаем пользователя по telegram id"""

        user: User | None = await self._session.scalar(
            select(User).where(
                User.telegram_id == user_tg_id,
            )
        )
        if not user:
            raise UserTgIdNotExist(user_tg_id)

        return convert_db_model_to_user_entity(user)

    @exception_mapper
    async def create_user(self, user: UserEntity) -> UserDTO:
        db_user = convert_user_entity_to_db_model(user)
        self._session.add(db_user)
        try:
            await self._session.flush((db_user,))
        except IntegrityError as err:
            self._parse_error(err)

        return convert_db_model_to_user_dto(db_user)

    @exception_mapper
    async def update_user(self, user: UserEntity) -> UserDTO:
        db_user = convert_user_entity_to_db_model(user)
        try:
            await self._session.merge(db_user)
        except IntegrityError as err:
            self._parse_error(err)

        return convert_db_model_to_user_dto(db_user)

    @staticmethod
    def _parse_error(err: DBAPIError) -> None:
        error_msg = err.__cause__.args[0]
        print(err.__cause__.args)
        print(error_msg)

        if "pk_user" in error_msg:
            raise UserIdAlreadyExist from err
        elif "uq_user_telegram_id" in error_msg:
            raise UserTgIdAlreadyExist from err
        else:
            raise RepoError from err
